package me.stone.stanimeclient.core.data.domain

import android.util.Log
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import kotlin.jvm.Throws

sealed class Result<out R> {

    data class Success<out T>(val data: T): Result<T>()

    data class Error(val error: Exception): Result<Nothing>()

    object Loading: Result<Nothing>()

    override fun toString(): String {
        return when(this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$error]"
            Loading -> "Loading"
        }
    }

    val isSuccess: Boolean
        get() = this is Success

    val isFailure: Boolean
        get() = this is Error

    val isLoading: Boolean
        get() = this is Loading

    val dataOrNull: R?
        get() = (this as? Success)?.data

}

inline fun <reified T> Result<T>.updateSuccess(stateFlow: MutableStateFlow<T>) {
    if (this is Result.Success) {
        stateFlow.value = data
    }
}


abstract class FlowUseCase<in P, T>(private val dispatcher: CoroutineDispatcher) {

    operator fun invoke(params: P): Flow<Result<T>> = execute(params)
        .catch { e ->
            Log.e("UseCase", "${e.message}", e)
            emit(Result.Error(Exception(e)))
        }
        .flowOn(dispatcher)

    abstract fun execute(params: P): Flow<Result<T>>

}

abstract class UseCase<in P, R> (
    private val dispatcher: CoroutineDispatcher
) {
    suspend operator fun invoke(params: P): Result<R> {
        return try {
            withContext(dispatcher) {
                execute(params).let { Result.Success(it) }
            }
        } catch (e: Exception) {
            Result.Error(e)
        }
    }

    @Throws(RuntimeException::class)
    abstract suspend fun execute(params: P): R
}